package main.java.com.async.controller;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSONObject;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import main.java.com.async.app.AsyncWoodTookitMain;
import main.java.com.async.app.MainDialog;
import main.java.com.async.middleware.JavaFxControllerMiddleware;
import main.java.com.async.middleware.MutipleTask;
import main.java.com.async.middleware.StageManager;
import main.java.com.async.models.AsyncFunction;
import main.java.com.async.tools.CustomizeUtil;
import main.component.infodialog.InfoDialogControl;
import com.ptc.cipjava.jxthrowable;
import com.ptc.pfc.pfcAsyncConnection.AsyncConnection;
import com.ptc.pfc.pfcModel.Model;
import com.ptc.pfc.pfcModel.ModelDescriptor;
import com.ptc.pfc.pfcSession.Session;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.text.TextFlow;
import org.apache.logging.log4j.core.config.LoggerConfig;

public class ModelLibraryController {
	private Session _session;
	private AsyncConnection _connection;

	private static String _current_item_path; //listview中所选项的路径
	private static String _current_dir_path; //当前文件夹路径
	private static String _current_model_path; //当前模型文件所在路径
	private static String _current_model_name; //当前模型名称
	private static String _model_lib_dir; //模型库根目录
	private static String _creo_lib; // jni的lib文件夹完整路径
	private static String _pro_comm_msg_dir; //Pro_Comm_Msg.exe 完整路径
	private static String _pro_dir; //Pro_Directory 完整路径

	private static final String ROOT_PATH = StageManager._var.get("root_path"); //当前项目的根目录
	private static final String CONFIG_FILE_PATH = StageManager._var.get("config_file_path"); // config文件完整路径
	private static final String NO_IMAGE_PATH = "/main/resources/images/no_image.png";

	// ---------------------------------------------------
	// 控件的id 在此处申明
	// ---------------------------------------------------
	@FXML
	private Button btnOpenModel; // 打开模型
	@FXML
	private Button btnAssembleModel; // 装配模型
	@FXML
	private Button btnInitialize; //初始化连接Creo
	@FXML
	private Button btnSetting; //配置程序的设定
	@FXML
	private Button btnAbout; //关于按钮
	@FXML
	private Button btnRename; //重命名
	@FXML
	private ComboBox<String> cbbModelCategory; //类别的下拉列表
	@FXML
	private ListView<String> lvModelList; //单个类别的列表
	@FXML
	private ImageView imgModelView; // 图片显示器
	@FXML
	private TextField txtFieldPrefix; // 前缀输入框
	@FXML
	private TextField txtFieldPostfix; // 后缀输入框
	@FXML
	private CheckBox checkboxPrefix; // 前缀勾选框
	@FXML
	private CheckBox checkboxPostfix; // 后缀勾选框
	@FXML
	private TextFlow textFlowInfo; //信息提示
	@FXML
	private ScrollPane scrollPaneInfo; //信息提示滚动容器
	@FXML
	private Pane paneModelLib; //模型库对话框的主界面
	@FXML
	private ProgressIndicator progressIndicatorLoading; //连接Creo时的进度条
	// ---------------------------------------------------

	/**
	 * Control类初始化函数,处于生命周期中最早的阶段
	 *
	 * @throws IOException
	 * @throws jxthrowable
	 */
	@FXML
	private void initialize() throws IOException {
		paneModelLib.setDisable(true);
	}

	// ---------------------------------------------------
	// 控件动作事件
	// ---------------------------------------------------

	/**
	 * 初始化Creo程序
	 *
	 * @throws IOException
	 * @throws jxthrowable
	 * @throws InterruptedException
	 */
	@FXML
	private void onClickBtnInitialize() throws IOException, jxthrowable, InterruptedException {
		JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo, "连接Creo中，请耐心等候...", null);
		initializeSetting();
//		如果设置pro_comm_msg.exe的路径才可初始化
		if (_pro_comm_msg_dir.contains("pro_comm_msg.exe") && _pro_dir != null) {
//			显示进度条
			progressIndicatorLoading.setVisible(true);
			if (_connection == null) {
//				新开启一个线程连接Creo
				CreoLoadingTask creoLoadingTask = new CreoLoadingTask(_pro_comm_msg_dir, _pro_dir, _creo_lib);
				Thread creoThread = new Thread(creoLoadingTask);
				creoThread.setDaemon(true);
				creoThread.start();
//				监听子线程任务的返回值
				creoLoadingTask.valueProperty().addListener(new ChangeListener<Boolean>() {
					@Override
					public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
						if (newValue) {
							JavaFxControllerMiddleware.displayCreoInfo(textFlowInfo, scrollPaneInfo);
							_session = creoLoadingTask.getSession();
							_connection = creoLoadingTask.getConnection();
//							隐藏进度条
							progressIndicatorLoading.setVisible(false);
							paneModelLib.setDisable(false);
							btnSetting.setDisable(true);
//							为防止OOM,初始化后将StageManager置空
							StageManager._var = null;
							StageManager._connection.put("connection", _connection);
						}
					}
				});
			} else {
				progressIndicatorLoading.setVisible(false);
				JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo, "程序已和Creo连接了!", null);
			}
		} else {
			InfoDialogControl infoDialogControl = new InfoDialogControl(AlertType.WARNING, null,
					"pro_comm_msg.exe路径不正确!", StageManager._stage.get("mainStage"));
			infoDialogControl.show();
		}

	}

	/**
	 * 打开设置窗口
	 *
	 * @throws IOException
	 */
	@FXML
	private void onClickBtnSetting() throws IOException {
		MainDialog mainDialog = new MainDialog();
		mainDialog.popSettingDialog();
	}

	@FXML
	private void onClickBtnAbout() throws jxthrowable {
		InfoDialogControl infoDialogControl = new InfoDialogControl(AlertType.INFORMATION, "关于",
				"Version: beta 0.1 \n Dev by Lin", StageManager._stage.get("mainStage"));
		infoDialogControl.show();
	}

	/**
	 * 打开模型的按钮事件
	 *
	 * @throws jxthrowable
	 * @throws FileNotFoundException
	 */
	@FXML
	private void onClickActionBtnOpenModel() throws jxthrowable, FileNotFoundException, NoSuchMethodException {
		if (_current_model_path != null) {
//			将模型的路径替换为绝对路径
			String modelAbsolutePath = ROOT_PATH + _current_model_path.replace("/", "\\");
//            调用方法的准确名称
			String methodName = "openModel";
//            调用方法的字符串参数
			String[] params = new String[]{modelAbsolutePath, _current_model_name};

			runTask(methodName, params, "模型已打开");
		}
	}

	/**
	 * 装配模型的按钮事件
	 *
	 * @throws jxthrowable
	 * @throws FileNotFoundException
	 */
	@FXML
	private void onClickActionBtnAssembleModel() throws jxthrowable, FileNotFoundException, NoSuchMethodException {
		if (getCurrentSessionModelCategory().equals("asm") && _current_model_path != null) {
			String modelAbsolutePath = ROOT_PATH + _current_model_path.replace("/", "\\");
			String methodName = "assembleModel";
			String[] params = new String[]{modelAbsolutePath, _current_model_name};
			runTask(methodName, params, "已插入零件!");
		}
		else if (!getCurrentSessionModelCategory().equals("asm")) {
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"当前Creo内必须为装配件!", "red");
		}
	}

	/**
	 * 依据前缀后缀重命名当前窗口内的模型名称
	 * @throws jxthrowable
	 */
	@FXML
	void onClickActionBtnRename() throws jxthrowable, NoSuchMethodException {
		Model curModel = _session.GetCurrentModel();
		if(curModel != null){
			String curModelFileName = curModel.GetFileName().toUpperCase();
			String content = "是否对 " + curModelFileName + " 模型进行重命名？";
			InfoDialogControl infoDialogControl = new InfoDialogControl(AlertType.CONFIRMATION, "是否继续",
					content, StageManager._stage.get("mainStage"));
			String prefix = txtFieldPrefix.getText();
			String postfix = txtFieldPostfix.getText();
			String specialSymbol = "CWV";
			Optional<ButtonType> result = infoDialogControl.showAndWait();
			if(result.isPresent() && result.get() == ButtonType.OK) {
				if(checkNewModelNameValidate(prefix, _session.GetCurrentModel().GetInstanceName(), postfix, specialSymbol)){
					String methodName = "renameCurModel";
					String[] params = new String[]{prefix, postfix, specialSymbol};
					runTask(methodName, params, "重命名成功!");
				}
			}
		}
		else {
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"当前窗口内无模型!", "red");
		}

	}

	/**
	 * ComBobox的选择事件
	 * 功能描述：ComBobox选择了模型类别后将类别文件夹下文件所有名字呈现在ListView中
	 */
	@FXML
	private void onSelectAcitonCbbModelCategory() {
		String itemName = cbbModelCategory.getSelectionModel().getSelectedItem();
		_current_item_path = _model_lib_dir + "/" + itemName;
		lvModelList.setItems(getModelDirectoryList(_current_item_path));
	}

	/**
	 * listview的鼠标点击事件
	 * 描述：双击listview内容，判断为文件夹并进入。单击选择仅获取每个文件或文件夹的路径和完整名称,同时有图片就添加图片
	 *
	 * @param event 鼠标事件
	 */
	@FXML
	private void onMouseClickLvModelist(MouseEvent event) {
		String itemName = lvModelList.getSelectionModel().getSelectedItem();
//		若每次重新载入的_current_item_path路径中有Creo文件,说明路径拼接不正确,将当前路径重置为当前文件夹路径
//		出现拼接错误的原因是,JAVAFX的鼠标监听函数可能把双击识别为单击
		if (_current_item_path != null && checkIsCreoFile(_current_item_path)) {
			_current_item_path = _current_dir_path;
		}
//		双击的对象是文件夹就更新_current_item_path参数
		if (event.getClickCount() == 2 && checkFileIsDirectory(_current_item_path)) {
//			备份当前文件夹路径
			_current_dir_path = _current_item_path;
//			拼接获取最新的模型路径
			_current_item_path = _current_item_path + "/" + itemName;
			if (checkFileIsDirectory(_current_item_path)) {
				lvModelList.setItems(getModelDirectoryList(_current_item_path));
			}
		}
		// 获取单击的完整的Creo文件名
		else if (checkIsCreoFile(itemName)) {
			_current_model_path = _current_item_path + "/";
			_current_model_name = itemName;
//			设置图片
			String imgFilePath = _current_model_path.concat(itemName.split("\\.")[0].concat(".png"));
			if (CustomizeUtil.checkFileExistStatic(imgFilePath)) {
				imgModelView.setImage(new Image("file:" + imgFilePath));
			} else {
				imgModelView.setImage(new Image(NO_IMAGE_PATH));
			}
		}
	}

	/**
	 * 前缀CheckBox的勾选事件
	 * 描述：前缀勾选框被选中则输入框可用,否则不行
	 */
	@FXML
	private void onClickActionCheckboxPrefix() {
		if (checkboxPrefix.isSelected()) {
			txtFieldPrefix.setDisable(false);
		} else {
			txtFieldPrefix.clear();
			txtFieldPrefix.setDisable(true);
		}
	}

	/**
	 * 后缀CheckBox的勾选事件
	 * 描述：后缀勾选框被选中则输入框可用,否则不行
	 */
	@FXML
	private void onClickActionCheckboxPostfix() throws jxthrowable {
		if (checkboxPostfix.isSelected()) {
			txtFieldPostfix.setDisable(false);
		} else {
			txtFieldPostfix.clear();
			txtFieldPostfix.setDisable(true);
		}
	}

	// ---------------------------------------------------
	// 自定义函数
	// ---------------------------------------------------

	/**
	 * 初始化启动Creo连接所需的配置
	 */
	private void initializeSetting() throws IOException {
//		初始化配置
		loadConfigSetting();
//		在ComBobox中初始化
		ObservableList<String> cbbContent = getModelDirectoryList(_model_lib_dir);
//		由于cbbContent第一行含有../字符串,显示是后需要去除
		cbbContent.remove(0, 1);
		cbbModelCategory.setItems(cbbContent);
	}

	/**
	 * 获取指定文件夹下的所有文件名
	 *
	 * @param dirPath 指定的文件夹路径
	 * @return 按照文件夹在上文件在下的格式返回
	 */
	private ObservableList<String> getModelDirectoryList(String dirPath) {
		ObservableList<String> dirNameList = FXCollections.observableArrayList();
		ObservableList<String> fileNameList = FXCollections.observableArrayList();
//		检查提供的路径是否存在
		if (!CustomizeUtil.checkFileExistStatic(dirPath)) {
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo, dirPath + "路径不存在!", "red");
			return dirNameList;
		}
//		添加后退标识
		dirNameList.add("../");
		File file = new File(dirPath);
		File[] files = file.listFiles();
		String fileName = null;
		for (File eachFile : files) {
			fileName = eachFile.getName();
			if (eachFile.isDirectory()) {
				dirNameList.add(fileName);
			}
//			检测是否为Creo文件
			else if (checkIsCreoFile(fileName)) {
//				去除文件名的扩展名
				fileNameList.add(removeCreoFileVersionNum(fileName));
			}
		}
//		对Creo文件名去重再合并到dirNameList中
		dirNameList.addAll(fileNameList.stream().distinct().collect(Collectors.toList()));
		return dirNameList;
	}

	/**
	 * 检查当前路径下的文件是否是文件夹
	 *
	 * @param path 文件路径
	 * @return
	 */
	private Boolean checkFileIsDirectory(String path) {
		File file = new File(path);
		if (file.isDirectory()) {
			return true;
		}
		return false;
	}

	/**
	 * 判断文件是否是Creo的文件
	 *
	 * @param creoFileName 包含扩展名在内的文件名
	 * @return 如果是Creo就返回true, 否则返回false
	 */
	private Boolean checkIsCreoFile(String creoFileName) {
//		这里正则表达式需要说明,结尾采用可有版本号或者没有版本号,是为了兼顾检测listview里面对象
		if (creoFileName == null) {
			return false;
		}
		String creoFileFilterRegex = ".+(\\.prt|\\.asm).*";
		return Pattern.matches(creoFileFilterRegex, creoFileName);
	}

	private String removeCreoFileVersionNum(String creoFileName) {
//		判断文件最后是否是以'.数字'的方式结尾
		String versionNumRegex = ".*\\.[0-9]+$";
		if (Pattern.matches(versionNumRegex, creoFileName)) {
			String[] splitStr = creoFileName.split("\\.");
			return splitStr[0] + "." + splitStr[1];
		}
		return creoFileName;
	}

	/**
	 * 加载config中的配置内容,初始化变量
	 *
	 * @throws IOException
	 */
	private void loadConfigSetting() throws IOException {
		String config = CustomizeUtil.readFileToString(CONFIG_FILE_PATH);
//		config加载为空或失败就退出程序
		if (config == null) {
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo, "config为空,请检查config内容及路径!", "red");
			return;
		}
//		解析字符串为json对象
		JSONObject jsonObject = JSONObject.parseObject(config);
//		加载config中设置的路径
		try {
			_model_lib_dir = jsonObject.getString("ModelLibraryPath");
			_creo_lib = jsonObject.getString("CreoLibPath");
			_pro_comm_msg_dir = jsonObject.getString("ProCommMsg");
			_pro_dir = jsonObject.getString("ProDirectory");
		} catch (Exception e) {
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"config中key参数缺失或格式错误!", "red");
		}
	}

	/**
	 * 获取当前窗口内的模型类别
	 *
	 * @return 返回扩展名
	 * @throws jxthrowable
	 */
	private String getCurrentSessionModelCategory() throws jxthrowable {
		Model curModel = _session.GetCurrentModel();
		if (curModel == null) {
			return "";
		}
		ModelDescriptor modelDescriptor = curModel.GetDescr();
		return modelDescriptor.GetExtension();
	}

	/**
	 * 根据前缀和后缀生成新的模型名字,模型名+前后缀字符串不得超过30个,否则提示用户修改模型名称
	 *
	 * @param prefix           前缀
	 * @param currentModelName 当前的模型名
	 * @param postfix          后缀
	 * @param specialSymbol    特殊字符
	 * @return true or false
	 */
	private Boolean checkNewModelNameValidate(String prefix, String currentModelName, String postfix, String specialSymbol) {
		if(prefix.trim().length() == 0 && postfix.trim().length() == 0){
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"前缀和项目号不能同时为空!", "red");
			return false;
		}

//		若输入框可以使用且前缀符合规则但大于6个字符则截取
		if (Pattern.matches("^[A-Za-z0-9_]+$", prefix) && prefix.length() > 6) {
			txtFieldPrefix.setText(prefix.substring(0, 6).trim());
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"前缀长度应该小于等于6位, 现已截取!", "red");
			return false;
		}
//		如果不符合规则,则将内容设置为空
		else if ( prefix.length()!= 0 && !Pattern.matches("^[A-Za-z0-9_]+$", prefix)) {
			txtFieldPrefix.clear();
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"前缀不符合规则,请重输!", "red");
			return false;
		}

//		勾选了项目号输入栏但必须填写的项目号不符合规则,那么清空输入框内容
		if (!txtFieldPostfix.isDisable() && !Pattern.matches("^\\d{6}$", postfix)) {
			txtFieldPostfix.clear();
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"项目号必须全为数字,且长度为6位!", "red");
			return false;
		}

		Integer newModelNameLen = currentModelName.length();
		if (prefix.length() != 0) {
			newModelNameLen = prefix.length() + 1 + newModelNameLen;
		}
		if (postfix.length() != 0) {
			newModelNameLen = newModelNameLen + 1 + specialSymbol.length() + postfix.length();
		}
		if (newModelNameLen > 30) {
			JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo,
					"新模型名已超30个字符,无法重命名!", "red");
			return false;
		}
		return true;
	}

	/**
	 * 新起一个线程,用于运行Creo的方法,同时会显示进度条,在监听器收到结束后会隐藏进度条的显示,同时在信息栏显示通知
	 * @param methodName 调用的AsyncFunction的方法名
	 * @param params 被调用的方法的参数,按照顺序要求依次填写
	 * @param finishInfo 结束后的通知信息
	 * @throws NoSuchMethodException
	 */
	private void runTask(String methodName, String[] params, String finishInfo) throws NoSuchMethodException {
		MutipleTask mutipleTask = new MutipleTask(new AsyncFunction(), methodName, _session, params);
		progressIndicatorLoading.setVisible(true);
		mutipleTask.start();
		mutipleTask.valueProperty().addListener(new ChangeListener<Boolean>() {
			@Override
			public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
				if(newValue){
					progressIndicatorLoading.setVisible(false);
					JavaFxControllerMiddleware.infoToUser(textFlowInfo, scrollPaneInfo, finishInfo, null);
				}
			}
		});
	}


}



class CreoLoadingTask extends Task<Boolean>{

	private String _pro_comm_msg_path = null;
	private String _pro_directory = null;
	private String _jni_lib_path = null;

	private AsyncWoodTookitMain _asyncwoodtookitmain;

	public CreoLoadingTask(String pro_comm_msg_path, String pro_directory, String jniLibPath){
		_pro_comm_msg_path = pro_comm_msg_path;
		_pro_directory = pro_directory;
		_jni_lib_path = jniLibPath;
	}

	public Session getSession(){
		return _asyncwoodtookitmain.getSession();
	}

	public AsyncConnection getConnection(){
		return _asyncwoodtookitmain.getConnection();
	}

	@Override
	protected void updateValue(Boolean value) {
		super.updateValue(value);
	}

	@Override
	protected Boolean call() throws Exception {
		//非常重要,下面两个系统变量必须添加,否则不能运行
		if(!System.getenv("path").contains(_jni_lib_path)){
			setEnviromentVar(String.format("setx path \"%s\"", _jni_lib_path));
		}
		if(!System.getenv("PRO_COMM_MSG_EXE").contains(_pro_comm_msg_path)){
			setEnviromentVar(String.format("setx PRO_COMM_MSG_EXE \"%s\"", _pro_comm_msg_path));
		}
		if(!System.getenv("PRO_DIRECTORY").contains(_pro_directory)){
			setEnviromentVar(String.format("setx PRO_DIRECTORY \"%s\"", _pro_directory));
		}
		_asyncwoodtookitmain = new AsyncWoodTookitMain();
		_asyncwoodtookitmain.initializeCreo();
		return true;
	}

	/**
	 * 执行CMD命令
	 * @param cmd CMD命令的字符串
	 * @throws IOException
	 * @throws InterruptedException
	 */
	private void setEnviromentVar(String cmd) throws IOException, InterruptedException {
		Process process = Runtime.getRuntime().exec(cmd);
		process.waitFor();
		process.destroy();
	}

	public static void setLibraryPath(String path) throws Exception {
		System.setProperty("java.library.path", path);
		//set sys_paths to null
		final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
		sysPathsField.setAccessible(true);
		sysPathsField.set(null, null);
	}
}
